/*****************************************************************************
 * Copyright (c) 2010 Atos Origin.
 *
 *
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Atos Origin - Initial API and implementation
 *
 *****************************************************************************/
package org.eclipse.papyrus.uml.diagram.activity.edit.dialogs;

import java.util.Collections;
import java.util.List;

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.viewers.ComboViewer;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.Window;
import org.eclipse.papyrus.infra.ui.util.EditorUtils;
import org.eclipse.papyrus.uml.diagram.activity.part.CustomMessages;
import org.eclipse.papyrus.uml.diagram.activity.part.UMLDiagramEditorPlugin;
import org.eclipse.papyrus.uml.diagram.activity.providers.UMLElementTypes;
import org.eclipse.papyrus.uml.diagram.common.actions.LabelHelper;
import org.eclipse.papyrus.uml.diagram.common.ui.helper.HelpComponentFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.forms.FormDialog;
import org.eclipse.ui.forms.IManagedForm;
import org.eclipse.ui.forms.widgets.ExpandableComposite;
import org.eclipse.ui.forms.widgets.FormToolkit;
import org.eclipse.ui.forms.widgets.ImageHyperlink;
import org.eclipse.ui.forms.widgets.ScrolledForm;
import org.eclipse.ui.forms.widgets.Section;
import org.eclipse.uml2.uml.NamedElement;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.ParameterDirectionKind;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.UMLPackage;

/**
 * DialogBox in order to link a parameter with the new ActivityParameterNode
 * that will be created
 *
 */
public class CreateParameterDialog extends FormDialog {

	private Text creationNameText;

	private Text creationTypeText;

	private Button creationTypeButton;

	private Parameter createdParameter = null;

	private EObject selectedType = null;

	private NamedElement parameterOwner;

	private String selectedName = null;

	private ParameterDirectionKind selectedDirection = null;

	private ComboViewer directionComboViewer = null;

	private Combo creationDirectionCombo = null;

	private ILabelProvider labelProvider;

	private ParameterDirectionKind defaultDirection = null;

	/**
	 * Create a new dialog to initialize an ActivityParameterNode.
	 *
	 * @param shell
	 *            parent shell
	 * @param owner
	 *            the activity that owns the action
	 * @param defaultDirectionKind
	 *            the parameter direction kind to select by default (or null)
	 */
	public CreateParameterDialog(Shell shell, NamedElement owner, ParameterDirectionKind defaultDirectionKind) {
		super(shell);
		parameterOwner = owner;
		labelProvider = new AdapterFactoryLabelProvider(UMLDiagramEditorPlugin.getInstance().getItemProvidersAdapterFactory());
		defaultDirection = defaultDirectionKind;
	}

	/**
	 * Create the form to :
	 *
	 * - ask the user to choose or create an existing element.
	 *
	 * @see org.eclipse.ui.forms.FormDialog#createFormContent(org.eclipse.ui.forms.IManagedForm)
	 */
	@Override
	protected void createFormContent(IManagedForm pForm) {
		pForm.getForm().setText(getTitle());
		ScrolledForm scrolledForm = pForm.getForm();
		FormToolkit toolkit = pForm.getToolkit();
		Composite parent = scrolledForm.getBody();
		parent.setLayout(new GridLayout());
		createParameterSection(scrolledForm.getBody(), toolkit);
		hookListeners();
		// invoked name is set after listeners, since we count on listener to
		// update it properly
		setInvokedName(null);
		scrolledForm.reflow(true);
	}

	/**
	 * Adds buttons to this dialog's button bar.
	 *
	 * @param parent
	 *            the button bar composite
	 */
	@Override
	protected void createButtonsForButtonBar(Composite parent) {
		super.createButtonsForButtonBar(parent);
		refreshOkButton();
	}

	/**
	 * Create the section to ask the user to create a parameter.
	 *
	 * @param pParent
	 *            the section's parent widget
	 * @param pToolkit
	 *            the form toolkit
	 */
	protected void createParameterSection(Composite pParent, FormToolkit pToolkit) {
		// create the section
		String lSectionTitle = getCreationTitle();
		Section lSection = pToolkit.createSection(pParent, ExpandableComposite.EXPANDED | ExpandableComposite.TITLE_BAR);
		lSection.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		if (lSectionTitle != null) {
			lSection.setText(lSectionTitle);
		}
		ImageHyperlink componentHelp = HelpComponentFactory.createHelpComponent(lSection, pToolkit, CustomMessages.CreateParameterDialog_ParameterCreationHelp, true);
		lSection.setTextClient(componentHelp);
		ScrolledForm lInsideScrolledForm = pToolkit.createScrolledForm(lSection);
		lInsideScrolledForm.setExpandHorizontal(true);
		lInsideScrolledForm.setExpandVertical(true);
		Composite lBody = lInsideScrolledForm.getBody();
		GridLayout lLayout = new GridLayout();
		lLayout.numColumns = 3;
		lBody.setLayout(lLayout);
		// content of the section
		pToolkit.createLabel(lBody, getNameLabel(), SWT.NONE);
		creationNameText = pToolkit.createText(lBody, "", SWT.BORDER);
		creationNameText.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
		creationNameText.setFocus();
		// manage type selection
		pToolkit.createLabel(lBody, getTypeLabel(), SWT.NONE);
		creationTypeText = pToolkit.createText(lBody, labelProvider.getText(selectedType), SWT.BORDER | SWT.READ_ONLY);
		creationTypeText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		creationTypeButton = pToolkit.createButton(lBody, "...", SWT.FLAT);
		Image image = getTypeImage();
		creationTypeButton.setImage(image);
		creationTypeButton.setLayoutData(new GridData(SWT.NONE));
		// manage direction selection
		pToolkit.createLabel(lBody, getDirectionLabel(), SWT.NONE);
		creationDirectionCombo = new Combo(lBody, SWT.DROP_DOWN | SWT.READ_ONLY);
		directionComboViewer = new ComboViewer(creationDirectionCombo);
		pToolkit.adapt(creationDirectionCombo);
		creationDirectionCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
		directionComboViewer.setLabelProvider(labelProvider);
		directionComboViewer.add(getPossibleDirections());
		// initialize selection
		directionComboViewer.setSelection(new StructuredSelection(getDefaultDirection()));
		selectedDirection = ParameterDirectionKind.getByName(getDefaultDirection());
		lInsideScrolledForm.reflow(true);
		lSection.setClient(lInsideScrolledForm);
	}

	/**
	 * Set correctly the invoked object, by creating it if needed. Then,
	 * notifies that the ok button of this dialog has been pressed.
	 *
	 * @see org.eclipse.jface.dialogs.Dialog#okPressed()
	 *
	 */
	@Override
	protected void okPressed() {
		// create element
		createdParameter = UMLFactory.eINSTANCE.createParameter();
		createdParameter.setName(selectedName);
		createdParameter.setType((Type) selectedType);
		createdParameter.setDirection(selectedDirection);
		addParameter(createdParameter);
		super.okPressed();
	}

	/**
	 * Get the invoked object that have been selected or created.
	 *
	 * @return the invoked object to use.
	 */
	public Parameter getCreatedParameter() {
		return createdParameter;
	}

	/**
	 * Add listeners to widgets
	 */
	private void hookListeners() {
		if (creationDirectionCombo != null && directionComboViewer != null) {
			// listener to select invocation type
			ModifyListener lTypeListener = new ModifyListener() {

				/**
				 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
				 */
				@Override
				public void modifyText(ModifyEvent e) {
					ISelection sel = directionComboViewer.getSelection();
					if (sel instanceof StructuredSelection) {
						String firstElement = ((StructuredSelection) sel).getFirstElement().toString();
						selectedDirection = ParameterDirectionKind.getByName(firstElement);
					} else {
						selectedDirection = null;
					}
					// reset name
					setInvokedName(null);
					refreshOkButton();
				}
			};
			creationDirectionCombo.addModifyListener(lTypeListener);
		}
		// listener to invocation element name
		ModifyListener lNameListener = new ModifyListener() {

			/**
			 * @see org.eclipse.swt.events.ModifyListener#modifyText(org.eclipse.swt.events.ModifyEvent)
			 */
			@Override
			public void modifyText(ModifyEvent e) {
				setInvokedName(creationNameText.getText());
			}
		};
		creationNameText.addModifyListener(lNameListener);
		// listener to select new element parent
		SelectionListener selectParentBtnListener = new SelectionAdapter() {

			/**
			 * @see org.eclipse.swt.events.SelectionAdapter#widgetSelected(org.eclipse.swt.events.SelectionEvent)
			 */
			@Override
			public void widgetSelected(SelectionEvent e) {
				handleChooseType();
				// reset name if not set
				if (selectedName == null) {
					setInvokedName(null);
				}
				refreshOkButton();
			}
		};
		creationTypeButton.addSelectionListener(selectParentBtnListener);
	}

	/**
	 * Set the name chosen for the invoked element
	 *
	 * @param text
	 *            the text string or null for auto-initialization
	 */
	private void setInvokedName(String text) {
		String name = text;
		if (text == null) {
			name = LabelHelper.INSTANCE.findName(parameterOwner, UMLPackage.eINSTANCE.getParameter());
			// the name assignment will be performed by listener's call
			creationNameText.setText(name);
		} else if (name != null && !"".equals(name.trim())) {
			selectedName = name.trim();
			Color black = creationNameText.getDisplay().getSystemColor(SWT.COLOR_BLACK);
			creationNameText.setForeground(black);
			refreshOkButton();
		} else {
			selectedName = null;
			Color red = creationNameText.getDisplay().getSystemColor(SWT.COLOR_RED);
			creationNameText.setForeground(red);
			refreshOkButton();
		}
	}

	/**
	 * Open the dialog to choose the type of element to create
	 *
	 */
	private void handleChooseType() {
		GetObjectsOfTypeListSelectionDialog dialog = new GetObjectsOfTypeListSelectionDialog(getShell(), labelProvider, parameterOwner, true);
		dialog.addElementsOfType(UMLPackage.eINSTANCE.getTypedElement_Type().getEType());
		if (dialog.open() == Window.OK) {
			Object firstResult = dialog.getFirstResult();
			if (firstResult instanceof EObject) {
				setType((EObject) dialog.getFirstResult());
			} else {
				setType(null);
			}
		}
	}

	/**
	 * Define the type of the object that will be created
	 *
	 * @param type
	 *            the selected type
	 */
	private void setType(EObject type) {
		selectedType = type;
		if (selectedType instanceof NamedElement) {
			creationTypeText.setText(labelProvider.getText(selectedType));
		} else {
			creationTypeText.setText("");
		}
	}

	/**
	 * Refresh the OK button activation
	 */
	private void refreshOkButton() {
		if (getButton(IDialogConstants.OK_ID) != null && !getButton(IDialogConstants.OK_ID).isDisposed()) {
			getButton(IDialogConstants.OK_ID).setEnabled(selectedDirection != null && selectedName != null);
		}
	}

	/**
	 * Add the created invoked object to its selected parent
	 */
	protected void addParameter(Parameter createdParameter) {
		TransactionalEditingDomain editingdomain = EditorUtils.getTransactionalEditingDomain();
		// Let the command find the relation on its own.
		Command addCmd = AddCommand.create(editingdomain, parameterOwner, null, Collections.singleton(createdParameter));
		addCmd.execute();
	}

	/**
	 * Gets the possible directions.
	 *
	 * @return the possible directions
	 */
	private String[] getPossibleDirections() {
		List<ParameterDirectionKind> values = ParameterDirectionKind.VALUES;
		String[] ret = new String[values.size()];
		for (int i = 0; i < values.size(); i++) {
			ret[i] = values.get(i).getName();
		}
		return ret;
	}

	/**
	 * Gets the direction which is selected by default.
	 *
	 * @return the default direction
	 */
	private String getDefaultDirection() {
		if (defaultDirection != null) {
			return defaultDirection.getName();
		} else {
			return getPossibleDirections()[0];
		}
	}

	private Image getTypeImage() {
		return UMLElementTypes.getImage(UMLPackage.eINSTANCE.getPackage_PackagedElement());
	}

	private String getTitle() {
		return CustomMessages.CreateParameterDialog_DialogTitle;
	}

	private String getCreationTitle() {
		return CustomMessages.CreateParameterDialog_ParameterCreationTitle;
	}

	private String getNameLabel() {
		return CustomMessages.CreateParameterDialog_NameLabel;
	}

	private String getTypeLabel() {
		return CustomMessages.CreateParameterDialog_TypeLabel;
	}

	private String getDirectionLabel() {
		return CustomMessages.CreateParameterDialog_DirectionLabel;
	}
}
